На этой странице мы покажем, как с помощью Python можно легко создать дополнительную функциональность. В этом упражнении мы создадим новый инструмент, который будет рисовать линию. Затем этот инструмент можно связать с командой FreeCAD, и эта команда может быть вызвана любым элементом интерфейса, например, пунктом меню или кнопкой на панели инструментов.
Сначала мы напишем скрипт, содержащий всю нашу функциональность. Затем мы сохраним его в файл и импортируем в FreeCAD, чтобы сделать доступными все его классы и функции. Запусти твой любимый редактор кода и введи следующие строки:
import FreeCADGui, Part
from pivy.coin import *
class line:
"""This class will create a line after the user clicked 2 points on the screen"""
def __init__(self):
self.view = FreeCADGui.ActiveDocument.ActiveView
self.stack = []
self.callback = self.view.addEventCallbackPivy(SoMouseButtonEvent.getClassTypeId(), self.getpoint)
def getpoint(self, event_cb):
event = event_cb.getEvent()
if event.getState() == SoMouseButtonEvent.DOWN:
pos = event.getPosition()
point = self.view.getPoint(pos[0], pos[1])
self.stack.append(point)
if len(self.stack) == 2:
l = Part.LineSegment(self.stack[0], self.stack[1])
shape = l.toShape()
Part.show(shape)
self.view.removeEventCallbackPivy(SoMouseButtonEvent.getClassTypeId(), self.callback)
import Part, FreeCADGui
from pivy.coin import *
В Python, когда ты хочешь использовать функции из другого модуля, сначала нужно импортировать его. В нашем случае нам понадобятся функции из модуля Деталь (Part) для создания линии и из модуля Gui FreeCADGui
для доступа к 3D виду. Нам также необходимо полное содержимое библиотеки Coin, чтобы мы могли напрямую использовать все объекты Coin, такие как SoMouseButtonEvent
и т.д.
class line:
Здесь мы определяем наш основной класс. Почему мы используем класс, а не функцию? Причина в том, что нам нужно, чтобы наш инструмент оставался "живым", пока мы ждём, когда пользователь нажмёт на экран. Функция завершается, когда её задача выполнена, а объект (класс определяет объект) остаётся живым, пока его не уничтожат
"""This class will create a line after the user clicked 2 points on the screen"""
В Python каждый класс или функция могут иметь строку документации (docstring). Это особенно полезно в FreeCAD, так как при вызове класса в интерпретаторе строка описания будет отображаться в виде всплывающей подсказки.
def __init__(self):
Классы Python всегда могут содержать функцию __init__
, которая выполняется при вызове класса для создания объекта. Сюда мы поместим всё, что должно произойти при запуске нашего инструмента для создания линии.
self.view = FreeCADGui.ActiveDocument.ActiveView
В классе обычно требуется добавлять к именам переменных self.
, чтобы переменные были легко доступны для всех функций внутри и вне класса. Здесь мы будем использовать self.view
для доступа и манипуляций с активным 3D-видом.
self.stack = []
Здесь мы создаём пустой список, который будет содержать 3D-точки, присланные функцией getpoint()
.
self.callback = self.view.addEventCallbackPivy(SoMouseButtonEvent.getClassTypeId(), self.getpoint)
Это очень важная часть. Поскольку мы имеем дело со сценой Coin3D, мы используем механизм обратного вызова Coin, который позволяет вызывать функцию каждый раз, когда происходит определённое событие сцены. В нашем случае мы создаём обратный вызов для событий SoMouseButtonEvent и привязываем его к функции getpoint()
. Теперь каждый раз, когда кнопка мыши будет нажата или отпущена, будет выполняться функция getpoint()
.
Обратите внимание, что существует также альтернатива addEventCallbackPivy()
под названием addEventCallback()
, которая не зависит от pivy. Но поскольку pivy - это очень эффективный и естественный способ доступа к любой части сцены Coin, он является лучшим выбором.
def getpoint(self, event_cb):
Теперь мы определим функцию getpoint()
, которая будет выполняться при нажатии кнопки мыши в 3D-виде. Эта функция будет получать аргумент, который мы назовём event_cb
. Из этого обратного вызова мы можем получить доступ к объекту события, который содержит несколько частей информации (подробнее здесь).
if event.getState() == SoMouseButtonEvent.DOWN:
Функция getpoint()
будет вызываться при нажатии или отпускании кнопки мыши. Но мы хотим выбрать 3D-точку только при нажатии кнопки, иначе мы получим две 3D-точки, расположенные очень близко друг к другу. Поэтому мы должны здесь это проверить.
pos = event.getPosition()
Здесь мы получаем экранные координаты курсора мыши.
point = self.view.getPoint(pos[0], pos[1])
Эта функция выдаёт нам вектор FreeCAD (x,y,z), содержащий 3D-точку, которая лежит в фокальной плоскости, прямо под курсором мыши. Если вы находитесь в режиме просмотра камеры, представьте себе луч, исходящий из камеры, проходящий через курсор мыши и попадающий в фокальную плоскость. Это и есть местоположение нашей 3D-точки. Если мы находимся в ортогональном режиме, то луч параллелен направлению обзора.
self.stack.append(point)
Мы добавляем нашу новую точку в стек (stack).
if len(self.stack) == 2:
Достаточно ли у нас уже точек? Если да, то давай рисовать линию!
l = Part.LineSegment(self.stack[0], self.stack[1])
Здесь мы используем функцию LineSegment()
из модуля Деталь (Part), которая создаёт линию из двух векторов FreeCAD. Линия не привязана ни к одному объекту в нашем активном документе, поэтому на экране ничего не появляется.
shape = l.toShape()
Документ FreeCAD может принимать только геометрические фигуры из модуля Деталь (Part). Фигуры (Shapes) - это наиболее общий тип в модуле Деталь (Part). Поэтому мы должны преобразовать нашу линию в фигуру, прежде чем добавлять её в документ.
Part.show(shape)
В модуле Деталь (Part) есть очень удобная функция show()
, которая создаёт новый объект в документе и привязывает к нему фигуру. Мы также могли бы сначала создать новый объект в документе, а затем привязать к нему фигуру вручную.
self.view.removeEventCallbackPivy(SoMouseButtonEvent.getClassTypeId(), self.callback)
Поскольку мы закончили работу с нашей линией, мы удалим здесь механизм обратного вызова.
Теперь давай сохраним наш скрипт в папке, где интерпретатор Python FreeCADа сможет его найти. При импорте модулей интерпретатор будет искать их в следующих местах: пути установки Python, папка FreeCAD bin и все папки FreeCAD Mod (папки с модулями). Поэтому лучшим решением будет создать новую папку в одной из папок Mod. Давайте создадим там папку MyScripts и сохраним в ней наш скрипт под именем exercise.py.
Теперь всё готово. Запустим FreeCAD, создадим новый документ и в интерпретаторе Python введём:
import exercise
Если сообщение об ошибке не появилось, наш учебный сценарий успешно загрузился. Теперь мы можем проверить его содержимое с помощью:
dir(exercise)
Команда dir()
- это встроенная команда Python, которая выводит список содержимого модуля. Мы можем проверить, что наш класс line()
находится именно там с помощью команды:
'line' in dir(exercise)
Приступим к тестированию:
exercise.line()
Щёлкни два раза в 3D-виде, и - бинго: вот наша линия! Чтобы повторить это, просто введите exercise.line()
ещё раз.
Чтобы наш новый инструмент для создания линий был действительно полезен, и чтобы не приходилось набирать всё это, у него должна быть кнопка в интерфейсе. Один из способов сделать это - превратить нашу новую папку MyScripts в полноценный верстак FreeCAD. Это легко, всё, что для этого нужно - поместить файл InitGui.py в папку MyScripts. InitGui.py будет содержать инструкции по созданию нового верстака и добавлению в него нашего нового инструмента. Кроме того, нам нужно будет немного изменить наш код упражнения, чтобы инструмент line()
распознавался как официальная команда FreeCAD. Начнём с создания файла InitGui.py и написания в нём следующего кода:
class MyWorkbench (Workbench):
MenuText = "MyScripts"
def Initialize(self):
import exercise
commandslist = ["line"]
self.appendToolbar("My Scripts", commandslist)
Gui.addWorkbench(MyWorkbench())
Теперь вы, вероятно, поняли суть приведённого выше скрипта. Мы создаём новый класс, который называем MyWorkbench
, даём ему название MenuText
, и определяем инициализирующую функцию Initialize()
, которая будет выполняться при загрузке верстака в FreeCAD. В этой функции мы загружаем содержимое нашего файла из упражнения и добавляем команды FreeCAD, находящиеся в нём, в список команд. Затем мы создаём панель инструментов под названием "My Scripts (Мои скрипты)" и назначаем для неё наш список команд. В настоящее время, конечно, у нас есть только один инструмент, поэтому наш список команд содержит только один элемент. Затем, когда наш верстак готов, мы добавляем его в основной интерфейс.
Но это всё равно не сработает, потому что для работы команды FreeCAD должны быть отформатированы определённым образом, и нам придётся изменить наш инструмент line()
. Наш новый скрипт exercise.py должен выглядеть следующим образом:
import FreeCADGui, Part
from pivy.coin import *
class line:
"""This class will create a line after the user clicked 2 points on the screen"""
def Activated(self):
self.view = FreeCADGui.ActiveDocument.ActiveView
self.stack = []
self.callback = self.view.addEventCallbackPivy(SoMouseButtonEvent.getClassTypeId(), self.getpoint)
def getpoint(self, event_cb):
event = event_cb.getEvent()
if event.getState() == SoMouseButtonEvent.DOWN:
pos = event.getPosition()
point = self.view.getPoint(pos[0], pos[1])
self.stack.append(point)
if len(self.stack) == 2:
l = Part.LineSegment(self.stack[0], self.stack[1])
shape = l.toShape()
Part.show(shape)
self.view.removeEventCallbackPivy(SoMouseButtonEvent.getClassTypeId(), self.callback)
def GetResources(self):
return {'Pixmap': 'path_to_an_icon/line_icon.png', 'MenuText': 'Line', 'ToolTip': 'Creates a line by clicking 2 points on the screen'}
FreeCADGui.addCommand('line', line())
Здесь мы преобразовали нашу функцию __init__()
в функцию Activated()
. Когда команды FreeCAD выполняются, они автоматически выполняют функцию Activated()
. Мы также добавили функцию GetResources()
, которая сообщает FreeCAD, где он может найти иконку для инструмента, а также название и всплывающую подсказку нашего инструмента. В качестве иконки подойдёт любое изображение jpg, png или svg, оно может быть любого размера, но лучше всего использовать размер, близкий к желаемым размерам, например 16x16, 24x24 или 32x32.
Затем мы добавим класс line()
в качестве официальной команды FreeCAD с помощью метода addCommand()
.
Вот и всё, теперь осталось перезапустить FreeCAD, и у нас будет новый верстак с новым инструментом для работы с линиями!
Если вам понравилось это упражнение, почему бы не попробовать улучшить этот маленький инструмент? Есть много вещей, которые можно сделать, например:
FreeCAD.Console
.input()
.Не стесняйтесь задавать вопросы и делиться идеями на форуме!